#include "swagme.h"
#include "sound.h"

int Sound::nFinishedChannels[NUM_AUDIO_CHANNELS];

Sound::Sound() :
    nextHandle(0)
{
  // Mix_OpenAudio() happens in main
  cout << "Sound: " << Mix_AllocateChannels(NUM_AUDIO_CHANNELS) << " channels allocated." << endl;
  cout << "Sound: " << Mix_ReserveChannels(NUM_AUDIO_CHANNELS) << " channels reserved." << endl;
  for (int i = 0; i < NUM_AUDIO_CHANNELS; i++) {
      nFinishedChannels[i]=1;
      nLastFinishedChannels[i]=1;
  }
}

void Sound::channelDone(int channel) {
  // this is not an atomic write, so there is a tiny space for a race condition here
  // only way to get around it is with a semaphore
//  cout << "Channel " << channel << " finished." << endl;
  Sound::nFinishedChannels[channel]=1;
}

void Sound::syncFinishedChannels() {
  for (int nChannel = 0; nChannel < NUM_AUDIO_CHANNELS; nChannel++) {
    if (nFinishedChannels[nChannel] && !nLastFinishedChannels[nChannel]) {
      // this channel just finished playing and the nameMap hasn't been updated
      if (nameMap.find(sChannelNameTable[nChannel]) != nameMap.end()) {
//        cout << "syncing, finished " << sChannelNameTable[nChannel] << endl;
        nameMap[sChannelNameTable[nChannel]]->channel = -1;
        sChannelNameTable[nChannel]="";
      } else {
        cerr << "Invalid sound on channel " << nChannel << " finished (" << sChannelNameTable[nChannel] << ")." << endl;
      }
    }
    nLastFinishedChannels[nChannel] = nFinishedChannels[nChannel];
  }
}

int const Sound::findFreeChannel() {
  for (int i = 0; i < NUM_AUDIO_CHANNELS; i++) {
    if (1 == nFinishedChannels[i]) {
      return i;
    }
  }
  return -1;
}

Uint32 Sound::Play(const string &name, bool loop /* =false */)
{
  int status = 0;
  int playCount = 0; // one time
  if (loop) playCount = -1; // endless loop
  syncFinishedChannels();
  if(nameMap.find(name) != nameMap.end()) {
    int ch;
    ch = findFreeChannel();
    if (-1 == ch) {
      cout << "Sound Error: Out of channels";
    } else {
      ch = Mix_PlayChannel(ch, nameMap[name]->pChunk, playCount);
    }
    if (-1 == ch) {
      cout << "Mixer Error: " << Mix_GetError() << endl;
      status = 3001;
    } else {
      nameMap[name]->channel = ch;
      // only if nameMap[name]->channel is valid
      sChannelNameTable[ch] = name;
      nFinishedChannels[ch] = 0;
      Mix_ChannelFinished(this->channelDone);
//      cout << " playing " << name << " on channel " << ch << endl;
    }
  } //  else { cout << " NOT playing " << name << endl;  }
  return status;
}

bool Sound::IsPlaying(const string &name)
{
  bool bResult = false;
  syncFinishedChannels();
  if ((nameMap.find(name) != nameMap.end()) && (nameMap[name]->channel > -1)) {
    bResult = (Mix_Playing(nameMap[name]->channel) != 0) ? true : false;
  }
  return bResult;
}

void Sound::Stop(const string &name) {
  syncFinishedChannels();
  if ((nameMap.find(name) != nameMap.end()) && (nameMap[name]->channel > -1)) {
    Mix_HaltChannel(nameMap[name]->channel); // always returns 0
  } else {
    // TODO: Figure out why std::cerr doesn't allow std::strings in Windows
    cerr << "Sound: Attempted to stop " << name << ", but it's not playing." << endl;
  }
}

Uint32 Sound::Load(const string &file, const string &name)
{
  Uint32 status=0;
  if (nameMap.find(name) == nameMap.end()) {
    LoadedSound *pls = new LoadedSound();
    if (pls) {
      pls->pChunk = Mix_LoadWAV(file.c_str());
    } else {
      cerr << "Sound: new failed for " << name << endl;
      exit(2012);
    }
    if (!pls->pChunk) {
      cerr << "Sound: failed to load " << file << ", Mixer Error: " << Mix_GetError() << endl;
      status = 3000;
    }
    nameMap[name] = pls;
  } else {
    cerr << "Sound: " << name << " already loaded." << endl;
  }
  cerr << "Sound: Returning status " << status << " for " << name << " loaded from " << file << endl;
  return status;
}

Uint32 Sound::Unload(const string &name)
{
  if(nameMap.find(name) != nameMap.end()) {
    if (IsPlaying(name)) Stop(name);
    if (nameMap[name]->pChunk) {
      Mix_FreeChunk(nameMap[name]->pChunk);
    }
    nameMap[name]->pChunk=NULL;
    delete nameMap[name];
    cerr << "Sound: Freed " << name << "." << endl;
    nameMap.erase(name);
  }
  return 0;
}

Sound::~Sound()
{
  // stop all playing sounds
  // unload them
  while(nameMap.size() > 0) {
    nameMapIter_t iter = nameMap.begin();
    cout << "Unloading sound " << iter->first << endl;
    Stop(iter->first);
    Unload(iter->first);
  }
  nameMap.clear();

//  int frequency;
//  Uint16 format;
//  int channels;
//  int numTimes = Mix_QuerySpec(&frequency, &format, &channels);
//  cout << "Sound: Mix_OpenAudio was called " << numTimes << " times." << endl;
  // free resources
  // Mix_CloseAudio(); happens in main() now
}

void Sound::dump() {
  cout << "** Sound dump, nameMap" << endl;
  for(nameMapIter_t iter = nameMap.begin(); iter!= nameMap.end(); ++iter ) {
    cout << iter->first << " " << iter->second << " " << iter->second->channel << " " << iter->second->pChunk << endl;
  }
  cout << "First free channel " << findFreeChannel() << endl;
  cout << "** Sound dump, finished channel flags" << endl;
  for (int idx = 0; idx < NUM_AUDIO_CHANNELS/2; ++idx) {
    cout << idx << sChannelNameTable[idx] << " " << nFinishedChannels[idx] << " " << nLastFinishedChannels[idx] << "   ";
    cout << idx + 8  << sChannelNameTable[idx + 8]  << " " << nFinishedChannels[idx + 8]  << " " << nLastFinishedChannels[idx + 8]  << endl;
//    cout << idx + 16 << sChannelNameTable[idx + 16] << " " << nFinishedChannels[idx + 16] << " " << nLastFinishedChannels[idx + 16] << "   ";
//    cout << idx + 24 << sChannelNameTable[idx + 24] << " " << nFinishedChannels[idx + 24] << " " << nLastFinishedChannels[idx + 24] << endl;
    }
}
